#!/usr/bin/env python
# -*- coding: utf-8 -*-
import io
import os
import re
import traceback
from collections.abc import Iterable
from datetime import datetime
from distutils.command.config import config
import random
from symtable import Function
from turtledemo.lindenmayer import replace

from click import option

from utility import tryUntilSuccess, lget, fprint
import pyperclip
from anyio import sleep
from attr.validators import disabled
from cryptography.fernet import Fernet
import PIL
import ddddocr
from PIL.ImageFile import ImageFile
from selenium.webdriver import ActionChains
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from selenium.common.exceptions import NoSuchElementException, TimeoutException, ElementNotInteractableException, JavascriptException, ElementClickInterceptedException
from selenium.webdriver.support.expected_conditions import element_selection_state_to_be
from selenium.webdriver.support.ui import Select
import win32con
import win32gui
from sympy import content
from win32api import Sleep
from pathlib import Path
import time
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from PIL import Image
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from  utility import datestrFormat, split_random_number, rename_with_pathlib, keep_english_digits_space
from collections.abc import Iterable
import pyautogui as pyautogui
import sys
import pandas as pd
from functools import partial


class CustomError(Exception):
    pass

class Pilot(object):
    # 初始化类的属性
    def __init__(self, login_info, driver=None, headless='N', mapping=None, platform=None, webType=None):
        # 无头模式
        # Y值优先
        if login_info.get('headless') == "Y" or headless == "Y":
            self.__headless = "Y"
        else:
            self.__headless = login_info.get('headless')
        print(headless)
        option = Options()
        # 禁用非必要功能
        option.add_argument("--disable-extensions")  # 禁用所有扩展
        option.add_argument("--disable-popup-blocking")  # 禁用弹窗拦截
        option.add_argument("--disable-notifications")  # 禁用通知
        option.add_argument("--disable-default-apps")  # 禁用默认应用
        #option.add_experimental_option("excludeSwitches", ["enable-automation"])  # 隐藏自动化标志
        # 内存优化
        option.add_argument("--disable-dev-shm-usage")  # 避免使用/dev/shm（Docker环境必备）
        option.add_argument("--no-sandbox")  # 禁用沙盒（安全风险，仅测试环境使用）
        if self.__headless == "Y":
            option.add_argument('--headless=new')  # 浏览器不会显示界面，程序在后台运行
            option.add_argument('--ignore-certificate-errors')  # 提示‘您的链接不是私密连接，攻击者...’时使用该参数，可忽略证书错误
            option.add_argument("--disable-gpu")  # 禁用 GPU 加速（可选）
            option.add_argument("--window-size=1920,1080")  # 设置窗口大小（可选）
            option.add_argument("--disable-dev-shm-usage")  # 避免/dev/shm不足
            option.add_argument("--disable-extensions")  # 禁用扩展
            option.add_argument("--no-sandbox")  # 禁用沙盒
            # option.add_argument("--log-level=3")  # 减少日志输出
            option.add_argument('--ignore-certificate-errors') # 忽略证书错误
            option.add_experimental_option("excludeSwitches", ["enable-logging"]) # 忽略浏览器日志
            # option.add_argument('--incognito')  # 设置浏览器无痕浏览->开启后确认会报错
            # option.add_argument("--disable-features=EnableNavigationPredictor")  # http有时会自动跳转为https，使用该参数可禁止重定向转为https
            # option.add_experimental_option("prefs",{"profile.managed_default_content_settings.images": 2})  # 不加载网页图片，可提高网页速度
            # option.add_experimental_option("detach", True)  # 程序结束后浏览器不会关闭
            # option.add_experimental_option("excludeSwitches", ['enable-automation'])  # 屏蔽webdriver特征，去掉谷歌正在受到测试软件的监控
            # option.add_experimental_option('excludeSwitches', ['enable-logging'])  # 若出现警告（Failed to read DnsConfig.），可使用此参数
        if not driver:
            service = Service(login_info.get('driver_path')) if login_info.get('driver_path') else None
            print("service:", service)
            if login_info['driver_type'] == "chrome":
                wd = webdriver.Chrome(service=service, options=option)
                wd.implicitly_wait(10)
                print("config配置的webdriver配置")
                self.__driver = wd
            else:
                print("外部的webdriver配置")
                self.__driver = webdriver.Firefox(service=service, options=option)
        else:
            self.__driver = driver
        suffix = f"_{webType}"
        if not login_info.get(f'url{suffix}'):
            print("系统的Url地址不能为空！")
            self.quit()
            sys.exit(1)
        self.__url = login_info[f'url{suffix}']
        self.__webType = webType
        cipher_suite = Fernet('-AtUkQo5RB3-qd1WW2HaqLJW3Eiq7IjJoBeVeaEDmlU=')
        self.__username = cipher_suite.decrypt(login_info[f'username{suffix}'] if suffix!="_warehouse" else login_info[f'username']).decode('utf-8')
        self.__password = cipher_suite.decrypt(login_info[f'password{suffix}'] if suffix!="_warehouse" else login_info[f'password']).decode('utf-8')
        # print(self.__username, self.__password)
        self.__receiver = login_info['receiver']
        self.__retry_times = login_info['retry_times']
        self.__captcha_method = login_info['captcha_method']
        self.__receiving_station = login_info['receiving_station']
        self.__failed = dict()
        self.__column_mapping = mapping or dict()
        self.__platform = platform
        print(login_info, self.__column_mapping, self.__platform)
        ## webdriver全局参数设置
        self.__driver.implicitly_wait(10)
        self.__login_info = login_info
        self._declaim_failed = set()
    # 打开网页的方法
    def openWebsite(self, url=None):
        self.__driver.maximize_window()
        self.__driver.get(url if url else self.__url)

    # 输入网页账号
    def inputUsername(self, find_element_method, element):
        print((find_element_method, element))
        WebDriverWait(self.__driver, 10).until(EC.presence_of_element_located((find_element_method, element)))
        usrName = self.__driver.find_element(find_element_method, element)
        usrName.clear()
        usrName.click()
        print("input ", self.__username)
        usrName.send_keys(self.__username)

    # 输入网页密码
    def inputPassword(self, find_element_method, element):
        WebDriverWait(self.__driver, 10).until(EC.presence_of_element_located((find_element_method, element)))
        passWrd = self.__driver.find_element(find_element_method, element)
        passWrd.clear()
        passWrd.click()
        passWrd.send_keys(self.__password)

    def getVerifyImg(self, find_element_method, element):
        WebDriverWait(self.__driver, 10).until(EC.visibility_of_element_located((find_element_method, element)))
        captcha_element = self.__driver.find_element(find_element_method, element)
        # 截取验证码图片
        captcha_screenshot = captcha_element.screenshot_as_png
        # 使用 PIL 打开图片
        image = Image.open(io.BytesIO(captcha_screenshot))
        return image, captcha_element

    # 识别验证码并且输入到文本框
    def verifyImgCode(self, find_image_method, image_element, find_text_method, text_element):
        # 读取验证码图片
        # img = Image.open('captchas/captcha_5.png')
        img, captcha_element = self.getVerifyImg(find_image_method, image_element)
        # imgCode.screenshot("code.png")
        # img = Image.open("code.png")
        # img.load()
        # txt = pytesseract.image_to_string(img, lang = "yilong") #识别验证码, 须提前对tesserct进行验证码训练,这里的训练包使用"yilong"是提前训练好的
        txt = Pilot.image2txt(img).strip()
        print(u"验证码:", txt)
        WebDriverWait(self.__driver, 5).until(EC.visibility_of_element_located((find_text_method, text_element)))
        txtCode = self.__driver.find_element(find_text_method, text_element)
        is_valid_code = (txt and len(txt)>=4)
        if is_valid_code:
            txtCode.send_keys(txt)
        return captcha_element, is_valid_code

    # 点击登陆
    def clickSubmit(self, find_element_method, element):
        WebDriverWait(self.__driver, 5).until(EC.visibility_of_element_located((find_element_method, element)))
        self.__driver.find_element(find_element_method, element).click()

    # 切换到frame中,如果账号密码输入框在frame框架内,须要先切换到frame中,不然是查找不到账号和密码输入框的元素的
    def switch2iframe(self, find_element_method, element):
        WebDriverWait(self.__driver, 10).until(EC.visibility_of_element_located((find_element_method, element)))
        self.__driver.switch_to_frame(self.__driver.find_element(find_element_method, element))

    @staticmethod
    def image2txt(img: ImageFile, method="ddddocr", test=False):
        if method == "ddddocr":
            ocr = ddddocr.DdddOcr()
            try:
                # 将图片转换为字节数据
                with io.BytesIO() as output:
                    img.save(output, format="PNG")
                    image_bytes = output.getvalue()
                # 识别验证码
                result = ocr.classification(image_bytes)
                if test: img.show()
                return result
                # img.show()
            except Exception as e:
                print('Error occurred:', e)
        else:
            # tesseract test.gif stdout -l foo --oem 3 --psm 6
            # img.load()
            # config = ('-l veryCode --oem 3 --psm 7')
            # result = pytesseract.image_to_string(img, config=config)
            # if test: img.show()
            # print("识别结果:", result)
            return None

    def inputTxt(self, find_element_method, element, content: str):
        print('{},"{}"'.format(element, content))
        el = WebDriverWait(self.__driver, 10).until(EC.visibility_of_element_located((find_element_method, element)))
        el.clear()
        el.click()
        el.send_keys(content)
        return el

    def inputTxt2(self, find_element_method, element, content: str):
        print('{},"{}"'.format(element, content))
        el = WebDriverWait(self.__driver, 10).until(EC.visibility_of_element_located((find_element_method, element)))
        el.clear()
        el.send_keys(content)
        return el

    def inputTxtEnter(self, find_element_method, el_selector, content: str, max_attempts=1):
        def doSth(el_selector, content):
            try:
                print('{},"{}"'.format(el_selector, content))
                el.clear()
                el.click()
                el.send_keys(content)
                # for char in content:
                #     el.send_keys(char)
                #     time.sleep(0.5)  # 添加延迟以模拟用户输入速度
                time.sleep(1)
                el.send_keys(Keys.ENTER)
            except:
                traceback.print_exc()
            # time.sleep(1)
            # el.send_keys(Keys.TAB)
        func = partial(doSth, el_selector, content)
        el = WebDriverWait(self.__driver, 10).until(EC.visibility_of_element_located((find_element_method, el_selector)))
        return self.tryUntilCondition(func, el, content, max_attempts=max_attempts, name=el_selector)
    
    def inputTxtChoose(self, el_selector, opts_el_selector, content: str, max_attempts=1):
        def doSth(element, optionElement, content):
            print('{},{},"{}"'.format(el_selector, opts_el_selector, content))
            el.clear()
            el.click()
            el.send_keys(content)
            WebDriverWait(self.__driver, 10).until(EC.visibility_of_element_located((By.XPATH, opts_el_selector))).click()
        el = WebDriverWait(self.__driver, 10).until(EC.visibility_of_element_located((By.XPATH, el_selector)))
        func = partial(doSth, el_selector, opts_el_selector, content)
        return self.tryUntilCondition(func, el, content, max_attempts=max_attempts, name=el_selector)
    
    def inputTxtWithCheck(self, find_element_method, element, content: str, max_tried_times=13):
        print('{},"{}"'.format(element, content))
        el = WebDriverWait(self.__driver, 10).until(EC.visibility_of_element_located((find_element_method, element)))
        tried_times = 0
        while True:
            tried_times += 1
            if tried_times> max_tried_times: break
            el.clear()
            el.click()
            el.clear()
            el.send_keys(content)
            sleep(3)
            if content==el.get_attribute("value"): break
        #el.send_keys(Keys.TAB)
        return el

    def inputSelect(self, find_element_method, element, content: str):
        print('input:{},"{}"'.format(element, content))
        el = None
        el = WebDriverWait(self.__driver, 10).until(EC.visibility_of_element_located((find_element_method, element)))
        # print("input el:", el)
        el.click()
        el.clear()
        print("send content:", content)
        el.send_keys(content)
        time.sleep(1)
        el.click()
        el.send_keys(Keys.TAB)
        return el

    def customSelect(self, element_select, element_option, element_select_method=By.XPATH, element_option_method=By.XPATH):
        print(element_select, element_option)
        # input box
        el = WebDriverWait(self.__driver, 10).until(EC.visibility_of_element_located((element_select_method, element_select)))
        el.click()
        # option list
        WebDriverWait(self.__driver, 10).until(EC.element_to_be_clickable((element_option_method, element_option))).click()
        return el

    def batchTryWithCheck(self, funcs:list[partial], max_attempts=13, check_times_after_success=1):
        try_times = 0
        while True:
            try_times += 1
            if try_times > max_attempts:
                raise TimeoutError("exceed max attempts！任务失败！")
            failed_funcs = []
            print("第{}次,tryWithBatchCheck".format(try_times))
            for f in funcs:
                if f()==False:
                    failed_funcs.append(f)
                    print("任务{}(({})执行失败！".format(f.func.__name__, ",".join([str(x) for x in f.args])))
            if len(failed_funcs) > 0 : 
                # 检查是全员的，因为成功不可靠会消失
                # funcs = failed_funcs
                time.sleep(1)
                continue
            else:
                if check_times_after_success > 0:
                    try_times = 0
                    check_times_after_success-=1
                    time.sleep(1)
                    print(f"任务成功！开始检查....")
                    continue
                else:
                    break
            # 默认全部函数任务重试；重试函数自身有检查机制，判断是否value已经一致
        print("tryWithBatchCheck完成！")
        
    def tryUntilCondition(self, func:partial, targetEl, targetVal, max_attempts=13, name="某字段"):
        try_times = 0 
        try:
            while targetVal not in targetEl.get_attribute('value'):
                try_times = try_times + 1
                if try_times > max_attempts:
                    print("exceed max attempts! give up temperally")
                    return False
                else:
                    print("{}：{},填写第{}次".format(name ,targetVal, try_times))
                    func()
                    time.sleep(0.8)
                    print("{}执行后, actual value:{}, targetValue:{}".format(name,targetEl.get_attribute('value'),targetVal)) 
            print(f"{name}完成！")
        except:
            traceback.print_exc()
            return False
        return True

    def tryUntilCondition2(self, funcs:list[partial], targetVal, name="某字段", max_attempts=4):
        try:
            for func in funcs:
                skip_outer = False
                targetEl = None
                try_times = 0
                while not targetEl or targetVal not in targetEl.get_attribute('value'):
                    try:
                        # i = try_times % len(funcs)
                        try_times = try_times + 1
                        if try_times > max_attempts:
                            print("exceed max attempts! give up temperally")
                            skip_outer = True
                            break
                        else:
                            print("{}：{},填写第{}次".format(name ,targetVal, try_times))
                            targetEl = func()
                            time.sleep(0.8)
                            print("{}执行后, actual value:{}, targetValue:{}".format(name,targetEl.get_attribute('value'),targetVal))
                    except:
                        traceback.print_exc()
                        continue
                if skip_outer:
                    continue
                print(f"{name}完成！")
                break
            if skip_outer:
                return False
        except:
            traceback.print_exc()
            return False
        return True

    def customSelect2(self, element_select, element_option_filter, value, element_select_method=By.XPATH,  element_option_filter_method=By.XPATH):
        print(element_select, element_option_filter)
        target = WebDriverWait(self.__driver, 10).until(EC.visibility_of_element_located((element_select_method, element_select)))
        try:
            actions = ActionChains(self.__driver)
            actions.double_click(target).perform()
            el_filter = WebDriverWait(self.__driver, 10).until(EC.visibility_of_element_located((element_option_filter_method, element_option_filter)))
            el_filter.clear();el_filter.send_keys(value)
           
            print("双击：","//div[text()='{}']".format(value))
            time.sleep(3)
            opt = WebDriverWait(self.__driver, 10).until(EC.visibility_of_element_located((By.XPATH, "//div[text()='{}']".format(value))))
            actions.double_click(opt).perform()
        except:
            traceback.print_exc()
        finally:
            target.send_keys(Keys.ESCAPE)
            target.send_keys(Keys.TAB)

    def win32gui_upload(self, filePath):
        # windows窗口操作
        ## 一级顶层窗口
        dialog = win32gui.FindWindow("#32770", "打开")
        ## 二级窗口
        comboBoxEx32 = win32gui.FindWindowEx(dialog, 0, "ComboBoxEx32", None)
        ## 三级窗口
        comboBox = win32gui.FindWindowEx(comboBoxEx32, 0, "ComboBox", None)
        ## 四级窗口 -- 文件路径输入区域
        edit = win32gui.FindWindowEx(comboBox, 0, "Edit", None)
        ## 二级窗口 -- 打开按钮
        button = win32gui.FindWindowEx(dialog, 0, "Button", None)
        ## 1、输入文件路径
        win32gui.SendMessage(edit, win32con.WM_SETTEXT, None, filePath)
        ## 2、点击打开按钮
        win32gui.SendMessage(dialog, win32con.WM_COMMAND, 1, button)

    def pyautogui_upload(self, filePath):
        try:
            # 定位上传文件的输入框
            #pyautogui.click(x=400, y=343)
            # 输入文件路径
            # pyautogui.typewrite(filePath)
            # # 按下回车键上传文件
            # pyautogui.press('enter')
            print("发送附件地址：", filePath)
            #pyautogui.typewrite(filePath, interval=0.1)
            #pyautogui.write('\n')
            pyperclip.copy(filePath)
            pyautogui.hotkey('ctrl', 'v')
            pyautogui.press('enter')
        except pyautogui.FailSafeException:
            print('Upload window closed or failed to locate upload box.')

    def scrollToBottom(self, driver):
        # driver.execute_script("""
        #   (function(){
        #         var y= document.body.scrollTop;
        #         var step = 100;
        #         window.scroll(0,y);
        #         function f() {
        #             if(y < document.body.scrollHeight){
        #                 y += step;
        #                 window.scroll(0,y);
        #                 setTimeout(f,50);
        #             }else{
        #                 window.scroll(0,y);
        #                 document.title += "scroll-done";
        #             }
        #           }
        #           setTimeout(f,1000);
        #     })();
        #     """)
        driver.execute_script("""
          (function(){
                var div = document.getElementsByClassName(".form-part-content");
                div[0].scrollTop = div[0].scrollHeight;
            })();
            """)


    def customUpload(self, step3: tuple, win_upload: Function, filePath:str, check_if_exists:bool=False):
        ## 滚动到页面底部, 到底方法都不管用
        # self.__driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
        # element = self.__driver.find_element(By.TAG_NAME, "body")
        # element.send_keys(Keys.END)
        # self.scrollToBottom(self.__driver)
        ## 问题的原因是：滚动条不是body的，是某个div的; 已经到底
        try:
            div_element = self.__driver.find_element(By.CSS_SELECTOR, '.form-part-content')
            self.__driver.execute_script("arguments[0].scrollTop = arguments[0].scrollHeight;", div_element)
            # 已有文件，则不上传 - 影响上传超时，暂时去掉
            print("check_if_exists:", check_if_exists)
            if check_if_exists:
                files = self.__driver.find_elements(By.XPATH, '//div[text()="附件上传"]/../..//table[@class="el-table__body"]//tr')
                if files: return 2
            print("开始上传....")
            # 点击上传会拉滚动; 上传元素检查
            step1 = (By.XPATH, '//div[text()="附件上传"]//../button')
            step2 = (By.CSS_SELECTOR, 'input[placeholder="请选择类型"]')
            step4 = (By.XPATH, '//button/span[text()="点击上传"]')
            step5 = (By.XPATH, '//footer//button/span[text()="确定"]')
            WebDriverWait(self.__driver, 10).until(EC.element_to_be_clickable((step1[0], step1[1]))).click()
            # step3 选类型
            actions = ActionChains(self.__driver)
            # 记录开始时间
            start_time = time.time()
            # WebDriverWait(self.__driver, 10).until(EC.presence_of_element_located((By.CSS_SELECTOR, "div.file-body-title")))
            # print("上传对话框出现...")
            choose_type = WebDriverWait(self.__driver, 10).until(EC.element_to_be_clickable((step2[0], step2[1])))
            print("请选择类型出现...")
            # 难的步骤重试3次
            try_times = 0
            while True:
                try:
                    if try_times > 5: return -1
                    try_times += 1
                    # if try_times%2 != 0:
                    # actions.move_to_element(choose_type).click().perform()
                    # else:
                    # choose_type = self.__driver.find_element(step2[0], step2[1])
                    actions.move_to_element(choose_type).click().perform()
                    # self.__driver.execute_script("arguments[0].click();", choose_type)
                    #self.__driver.execute_script("arguments[0].click();", choose_type)
                    print("第{}点击了".format(try_times))
                    EC_choice= WebDriverWait(self.__driver, 4).until(EC.element_to_be_clickable((step3[0], step3[1])))
                    EC_choice.click()
                    break
                except:
                    continue
            # 记录开始时间
            end_time = time.time();elapsed_time = end_time - start_time
            print(f"选择类型，代码运行时间: {elapsed_time} 秒")  # 最快0.9秒，程序没问题，多是网络有影响
            # 有时简单的 click() 方法无法解决问题，使用 ActionChains 模拟更复杂的用户操, 使用 ActionChains 模拟更复杂的用户操
            # actions = ActionChains(self.__driver)
            # actions.move_to_element(choose_type).click().perform()
            # WebDriverWait(self.__driver, 10).until(EC.presence_of_element_located((step2[0], step2[1]))).click()
            # step3 选类型->选项
            upload = WebDriverWait(self.__driver, 10).until(EC.element_to_be_clickable((step4[0], step4[1])))
            upload.click()
            print("开始上传!!")
            time.sleep(2)
            win_upload(filePath)
            time.sleep(2)
            try:
                WebDriverWait(self.__driver, 10).until(EC.presence_of_element_located((By.CSS_SELECTOR, '.file-name')))
            except Exception as e:
                print(e)
            ## 确认保存
            submit_button= WebDriverWait(self.__driver, 10).until(EC.element_to_be_clickable((step5[0], step5[1])))
            actions.move_to_element(submit_button).click().perform()
            # submit_button = self.__driver.find_element()
            # submit_button.click()
        except:
            return -1
        # 1.正常 2.已存在 -1报错失败
        return 1

    def selectTxt(self, find_element_method, element, content: str):
        select_element = WebDriverWait(self.__driver, 10).until(EC.visibility_of_element_located((find_element_method, element)))
        select = Select(select_element)
        # 通过 Select 对象选中小雷老师
        select.select_by_visible_text(content)

    def get_driver(self):
        return self.__driver

    def login(self, test=False):
        self.openWebsite()
        print("self.__webType:", self.__webType)
        if self.__webType == "warehouse":
            self.inputUsername(By.NAME, "username")
            self.inputPassword(By.NAME, "password")
        elif self.__webType == "yupei":
            self.__driver.switch_to.frame(self.__driver.find_element(By.XPATH, "(//iframe)[1]"))
            self.inputUsername(By.XPATH, '//input[@id="username"]')
            self.inputPassword(By.XPATH, '(//input[@id="password"])[1]')

        def verify_and_login():
            if self.__webType == "warehouse":
                catcha_element, is_valid_code = self.verifyImgCode(By.CSS_SELECTOR, "div.logo-img>img", "name", "verifyCode")
            elif self.__webType == "yupei":
                catcha_element, is_valid_code = self.verifyImgCode(By.XPATH, '//img[@id="verifyCodeImg"]', By.XPATH, '(//input[@id="verifyCode"])[1]')
            # 验证码空的话，重新提取验证码
            if not is_valid_code:
                catcha_element.click()
                if verify_and_login():
                    return True
            print("点击登录")
            if self.__webType == "warehouse":
                self.clickSubmit(By.CSS_SELECTOR, "button.login-foot-button")
            elif self.__webType == "yupei":
                self.clickSubmit(By.XPATH, '//a[@class="lgbtn lgbtn-common-login"]')
            try:
                WebDriverWait(self.__driver, 6).until(EC.url_changes(self.__url))
                print("登录成功")
                self.__driver.switch_to.default_content()
                return True
            except Exception as e:
                # # 检查 URL 是否变化
                # if self.__driver.current_url != self.__url:
                #     return True
                print("登录失败, {}".format(e))
                for i in range(self.__retry_times):
                    catcha_element.click()
                    if verify_and_login():
                        return True
                print(f'重新试{self.__retry_times}，仍然登录失败，程序退出！')

        verify_and_login()
        if test:
            time.sleep(10000) or input()

    def close_menu(self):
        print("关闭菜单...")
        # 点主菜单
        sub_menu = WebDriverWait(self.__driver, 10).until(EC.visibility_of_element_located((By.CSS_SELECTOR, 'div#tab-indwaybillQuery>span>div>svg')))
        sub_menu.click()

    def open_menu(self, menu_xpath_selector='//div[text()="代理人服务" and contains(@class,"sub-menu-title")]', submenu_selector='//li[text()="国际出港预录入" and contains(@role,"menuitem")]'):
        print("打开菜单...")
        # 点主菜单
        menu = WebDriverWait(self.__driver, 200).until(EC.visibility_of_element_located((By.XPATH, menu_xpath_selector)))
        menu.click()
        # 点子菜单
        sub_menu = WebDriverWait(self.__driver, 200).until(EC.visibility_of_element_located((By.XPATH, submenu_selector)))
        sub_menu.click()

    # 综合优化的查找函数
    def fast_find_elements(self, driver, selector, timeout=3):
        try:
            # 先尝试JavaScript快速查找
            print("execute_script检查....")
            elements = driver.execute_script(f"return document.querySelectorAll('{selector}');")
            if elements:
                # print("elements:", elements)
                return elements
            # 回退到显式等待的Selenium查找
            print("WebDriverWait检查....")
            driver.implicitly_wait(0)
            return WebDriverWait(driver, timeout).until(
                EC.presence_of_all_elements_located((By.CSS_SELECTOR, selector))
            )
        except:
            #traceback.print_exc()
            return []
        finally:
            driver.implicitly_wait(10)
            
    def search(self, keyword: dict ):
        try:
            if keyword["tp"] == 'billNo':
                self.inputTxt(By.XPATH, '(//div[text()="运单号"])[1]/..//input[@placeholder="请输入主单号"]', keyword['kw'])
            elif keyword["tp"] == 'frightDate':
                print("freightDate")
                self.inputTxt(By.XPATH, '(//label[text()="航班日期"])[1]/..//input', keyword['kw'])
            bt_start_dt = WebDriverWait(self.__driver, 4).until(EC.visibility_of_element_located((By.CSS_SELECTOR, 'input[placeholder="开始时间"]')))
            bt_start_dt.clear();bt_start_dt.send_keys("2004-03-01 00:00:00")
            WebDriverWait(self.__driver, 4).until(EC.visibility_of_element_located((By.CSS_SELECTOR, 'input[placeholder="结束时间"]'))).clear()
            WebDriverWait(self.__driver, 4).until(EC.visibility_of_element_located((By.XPATH, "//button/span[text()='查询']"))).click()
            result = set()
            def query_process():
                print("等待查询加载开始")
                try:
                    WebDriverWait(self.__driver, 3, poll_frequency=0.1, ignored_exceptions=(TimeoutException,NoSuchElementException,)).until(EC.visibility_of_element_located((By.CSS_SELECTOR, "svg.circular")))
                except:
                    pass
                print("等待查询加载结束")
                try:
                    WebDriverWait(self.__driver, 10, poll_frequency=0.1, ignored_exceptions=(TimeoutException,NoSuchElementException,)).until(EC.invisibility_of_element_located((By.CSS_SELECTOR, "svg.circular")))
                except:
                    pass
                result_els = self.fast_find_elements(self.__driver,  "div.table-content table.el-table__body>tbody>tr")
                #result_els = self.__driver.find_elements(By.CSS_SELECTOR, "div.table-content table.el-table__body>tbody>tr")
                #print("result_els:", result_els)
                for el in result_els:
                    e = el.find_element(By.CSS_SELECTOR, "td.el-table_1_column_3>div")
                    # 主单号处理，改成和excel一致
                    str1 = e.text
                    # print(e.text, str1)
                    if e: result.add(str1)
            # 第一结果是当前，不需要点击;其它页面需要
            query_process()
            # print("result:", result)
            next_page = self.__driver.find_element(By.CSS_SELECTOR, "button.btn-next")
            # 下一页方式遍历页面
            while True:
                is_disabled = next_page.get_attribute("disabled")
                if is_disabled: break
                next_page.click()
                query_process()
            # # 页码方式
            # result_pages = self.__driver.find_elements(By.CSS_SELECTOR, "ul.el-pager>li")
            # for index, element in enumerate(result_pages, start=3):
            #     print(f"{index}. {element.text}")
            #     element.click()
            #     query_process()
            # el-table_1_column_3
            # 使用 JavaScript 获取子元素的数量
            # child_count = self.__driver.execute_script("return arguments[0].childElementCount;", result_el)
            # print(child_count)
            # # print(billNo, result_el.get_attribute('innerHTML'))
            return result
        except NoSuchElementException as e:
            print(e)
            return set()
    
    def check_element_if_exist(self, xpath_selector:str, timeout:int=3):
        try:
            # 设置显式等待，默认最多等待3秒
            self.__driver.implicitly_wait(0)
            el = WebDriverWait(self.__driver, timeout).until(
                EC.presence_of_element_located((By.XPATH, xpath_selector))
            )
            return el
        except TimeoutException:
            return None
        finally:
            self.__driver.implicitly_wait(10)
    
    def check_if_exists(self, taskList: set):
        self.open_menu()
        self.tag_return("国际运单查询")
        try:
            exist_set = set()
            for task in taskList:
                if self.search({"tp":"billNo", "kw":task.replace("-", "")}):
                    print("task:{}", task)
                    exist_set.add(task)
            taskList.difference_update(exist_set)
            return exist_set
        except:
            traceback.print_exc()
        finally:
            self.close_menu()

    def batch_check_if_exists(self, taskList: set, kw: set, tp: str= "frightDate"):
        self.open_menu()
        self.tag_return("国际运单查询")
        # 高级查询开头只能点一次，再点就关闭
        self.__driver.find_element(By.CSS_SELECTOR, "div.text-btn-part>img").click()
        try:
            exist_dict = dict()
            for k in kw:
                res = self.search({"kw":k, "tp":tp})
                print("res:", res)
                exist_dict[k] = res
            #print("last result",exist_set)
            #input()
        except:
            traceback.print_exc()
        finally:
            self.close_menu()
            return exist_dict

    def page_turned(self, xpath_selector:str="//button/span[text()='返回']"):
        time.sleep(1)
        # 将 Esc 键发送到整个页面
        print("返回.....")
        body = self.__driver.find_element(By.TAG_NAME, "body")
        body.send_keys(Keys.ESCAPE)
        body.send_keys(Keys.ESCAPE)
        try:
            btn_return = (WebDriverWait(self.__driver, 10, poll_frequency=0.1, ignored_exceptions=(TimeoutException,NoSuchElementException,))
                          .until(EC.visibility_of_element_located((By.XPATH, xpath_selector))))
            if btn_return: btn_return.click()
        except Exception:
            traceback.print_exc()

    def tag_return(self,tag='主单预录入'):
        time.sleep(1)
        # 将 Esc 键发送到整个页面
        print("返回.....")
        body = self.__driver.find_element(By.TAG_NAME, "body")
        body.send_keys(Keys.ESCAPE)
        body.send_keys(Keys.ESCAPE)
        try:
            print("切换主单预录入:",'//div[text()="{}"]'.format(tag))
            btn_return = (WebDriverWait(self.__driver, 10, poll_frequency=0.1, ignored_exceptions=(TimeoutException,NoSuchElementException,))
                          .until(EC.visibility_of_element_located((By.XPATH, '//div[text()="{}"]'.format(tag)))))
            if btn_return: btn_return.click()
        except Exception:
            traceback.print_exc()

    def page_next(self):
        time.sleep(1)
        # 将 Esc 键发送到整个页面
        print("下一页.....")
        body = self.__driver.find_element(By.TAG_NAME, "body")
        body.send_keys(Keys.ESCAPE)
        body.send_keys(Keys.ESCAPE)
        try:
            btn_return = (WebDriverWait(self.__driver, 10, poll_frequency=0.1, ignored_exceptions=(TimeoutException,NoSuchElementException,))
                          .until(EC.visibility_of_element_located((By.XPATH, "//button/span[text()='下一票']"))))
            if btn_return: btn_return.click()
        except Exception:
            traceback.print_exc()

    def page_add(self, xpath_selector:str="//button/span[text()='主单新增']"):
        print("新增点击....")
        btn_add = WebDriverWait(self.__driver, 10).until(
            EC.visibility_of_element_located((By.XPATH, xpath_selector)))
        btn_add.click()

    def save(self):
        # 将 Esc 键发送到整个页面
        time.sleep(1)
        print("保存.....")
        body = self.__driver.find_element(By.TAG_NAME, "body")
        body.send_keys(Keys.ESCAPE)
        body.send_keys(Keys.ESCAPE)
        self.__driver.implicitly_wait(0)
        try:
            # self.__driver.find_element(By.XPATH, '').click()
            btn_save = (WebDriverWait(self.__driver, 6, poll_frequency=0.1, ignored_exceptions=(TimeoutException,NoSuchElementException,))
                          .until(EC.visibility_of_element_located((By.XPATH, "//button/span[text()='保存']"))))
            if btn_save: btn_save.click()
            try:
                btn_confirm = WebDriverWait(self.__driver, 4).until(EC.presence_of_element_located((By.XPATH, '//div[@class="el-message-box__btns"]//span[text()="取消"]')))
                if btn_confirm: btn_confirm.click()
                # if btn_save: btn_save.click()
            except:
                print("wait confirm error")
            return True
        except Exception:
            traceback.print_exc()
            return False
        finally:
            self.__driver.implicitly_wait(10)

    def send(self, billNo):
        time.sleep(3)
        body = self.__driver.find_element(By.TAG_NAME, "body")
        body.send_keys(Keys.ESCAPE)
        body.send_keys(Keys.ESCAPE)
        try:
            self.__driver.implicitly_wait(0)
            btn_send = WebDriverWait(self.__driver, 10).until(EC.element_to_be_clickable((By.XPATH, '//div[@class="opt-part"]//button/span[text()="发送"]')))
            print(btn_send.get_attribute("disabled"), btn_send.is_enabled())
            if not btn_send.get_attribute("disabled") and btn_send.is_enabled():
                  # el.click()
                print("send is available!")
                try_times = 0
                while True:
                    try:
                          try_times += 1
                          if try_times >3:
                              self.giveUp(billNo, "发送失败")
                              return False
                          self.__driver.execute_script("arguments[0].scrollIntoView();", btn_send)
                          actions = ActionChains(self.__driver)
                          actions.move_to_element(btn_send).click().perform()
                          btn_confirm = WebDriverWait(self.__driver, 6).until(EC.element_to_be_clickable((By.XPATH, '//button/span[text()="确认"]')))
                          # 再次使用 ActionChains 点击 btn_confirm
                          actions.reset_actions()  # 清除之前的动作（可选，避免意外累积）. 复用actions对象才需要
                          actions.move_to_element(btn_confirm).click().perform()
                          print(f"{billNo}, 发送完成...")
                          return True
                    except:
                        continue
            else:
                print(f"{billNo}, 发送不可用...")
                self.giveUp(billNo, "发送不可用")
                return False
            self.__driver.implicitly_wait(10)
        except:
            traceback.print_exc()
            self.giveUp(billNo, "发送失败")
            return False
    def giveUp(self, task, reason):
        if self.__platform == "GEN":
            reason = reason.replace("该单记录系统已经存在", "该单普货记录系统主单已经存在")
        self.__failed[task]=self.__failed.get(task, "") + ";" + reason
        print(task, reason)

    def data_check(self, billNo, mainBill_el):
        # 是否可保存判断
        mainBill_disabled = mainBill_el.get_attribute("disabled")
        mainBill_value = mainBill_el.get_attribute("value").strip()
        print("mainBill_disabled:", mainBill_disabled)
        print("mainBill_value:", mainBill_value)
        if mainBill_disabled:
            self.giveUp(billNo, "该单记录系统已经存在！")
            return False
        elif not mainBill_el.is_enabled() or not mainBill_el.is_displayed():
            self.giveUp(billNo, "录入信息验证不通过！")
            return False
        return True

    def Excel_yupei(self, row):
        result_dict =dict()
        billNo = getattr(row, self.__column_mapping.get("主单号", "主单号"))
        result_dict["billNo"] = billNo
        result_dict["billNo_prefix"], result_dict["billNo_suffix"] =billNo[:3], billNo[-8:]
        flightNo, flightDate = getattr(row, self.__column_mapping.get("航班号航班日期", "航班号航班日期")).split("/")
        flightNo = flightNo.replace("-", "").strip()
        result_dict["flightDate"] = datestrFormat(flightDate)
        result_dict["destPort"] = getattr(row, self.__column_mapping.get("货物目的地三字代码", "货物目的地三字代码"))
        result_dict["articles"] = getattr(row, self.__column_mapping.get("件数", "件数"))
        result_dict["weight"] = int(getattr(row, self.__column_mapping.get("重量", "重量")))
        result_dict["commodity"] = re.sub(r'[^a-zA-Z0-9]', ' ', getattr(row, self.__column_mapping.get("品名", "品名")))
        result_dict["commodity_code"] = "1200"
        result_dict["subBillNo"] = getattr(row, self.__column_mapping.get("分单号", "分单号"))
        result_dict["volume"] = getattr(row, self.__column_mapping.get("体积", "体积"), None)
        result_dict["transferPort"] = getattr(row, self.__column_mapping.get("中转港", "中转港"))
        result_dict["shipper2"] = getattr(row, self.__column_mapping.get("中转承运人", "中转承运人"))
        result_dict["recieverCountry"] = getattr(row, self.__column_mapping.get("收货人国家", "收货人国家"))
        result_dict["reciever"] = getattr(row, self.__column_mapping.get("收货人", "收货人"))
        result_dict["recieverAddr"] = getattr(row, self.__column_mapping.get("收货人地址", "收货人地址"))
        result_dict["senderCountry"] = getattr(row, self.__column_mapping.get("发货人国家", "发货人国家"))
        result_dict["sender"] = getattr(row, self.__column_mapping.get("发货人", "发货人"))
        result_dict["senderAddr"] = getattr(row, self.__column_mapping.get("发货人地址", "发货人地址"))
        result_dict["pingMing"] = getattr(row, self.__column_mapping.get("品名", "品名"))
        result_dict["flightCode"]  = getattr(row, self.__column_mapping.get("航班号航班日期", "航班号航班日期"))
        flightNo, flightDate = result_dict["flightCode"].split("/")
        result_dict["shipper"] = flightNo[:2].upper()
        result_dict["flight_number"] = flightNo[2:].replace("-", "")
        if result_dict.get("volume"):
            file_measure_weight = round(round(result_dict.get("volume"), 2) / 0.006)
            if file_measure_weight < 100 and result_dict.get("billNo_prefix") == "860": file_measure_weight = 100
            if file_measure_weight < 45 and result_dict.get("billNo_prefix") == "160": file_measure_weight = 45
            if file_measure_weight < result_dict.get("weight"): file_measure_weight = result_dict.get("weight")
            result_dict["file_measure_weight"] = file_measure_weight
        else:
            result_dict["file_measure_weight"] = result_dict.get("weight")
        cargoType = getattr(row, self.__column_mapping.get("货物类型", "货物类型"))
        if "电商" in cargoType or "快件" in cargoType:
            result_dict["cargoType"] = "跨境电商"
        elif "普货" in cargoType:
            result_dict["cargoType"] = "普货"
        else:
            result_dict["cargoType"] = "跨境电商"
        print("result_dict:", result_dict)
        return result_dict

    def Excel(self, df):
        res=list()
        #df[self.__column_mapping.get("主单号", "主单号")] = df[self.__column_mapping.get("主单号", "主单号")].ffill()
        mainBill = self.__column_mapping.get("主单号", "主单号"); subBill=self.__column_mapping.get("分单号", "分单号")
        cargoType = self.__column_mapping.get("货物类型", "货物类型"); PM= self.__column_mapping.get("品名", "品名")
        transPort = self.__column_mapping.get("中转港", "中转港"); shipper2 = self.__column_mapping.get("中转承运人", "中转承运人")
        articles = self.__column_mapping.get("件数", "件数"); weight = self.__column_mapping.get("重量", "重量")
        volume = self.__column_mapping.get("体积", "体积"); dimension = self.__column_mapping.get("尺寸数量", "尺寸数量")
        df[PM] = df[PM].ffill()
        # print("original df:",df)
        grouped_sum = df.groupby([cargoType, mainBill, self.__column_mapping.get("航班号航班日期", "航班号航班日期"),
                  self.__column_mapping.get("货物目的地三字代码", "货物目的地三字代码"), PM, transPort, shipper2
            ], dropna=False).agg({articles: lambda x: list(x), weight: 'sum', volume: 'sum', dimension: lambda x: ' '.join(x)}).reset_index()
        #[[self.__column_mapping.get("件数", "件数"), self.__column_mapping.get("重量", "重量")]].sum().reset_index()
        grouped_sum.columns = [cargoType, mainBill, self.__column_mapping.get("航班号航班日期", "航班号航班日期"),
                               self.__column_mapping.get("货物目的地三字代码", "货物目的地三字代码"),
                                PM, transPort, shipper2, articles, weight, volume, dimension]
        print("grouped_sum:", grouped_sum[["主单号", PM, transPort, shipper2, articles, weight, volume, dimension]])
        pattern = r'^[A-Za-z0-9]{2,20}$'  # 严格匹配 11 位纯数字
        df_sub = df[df["分单号"].str.contains(pattern, regex=True, na=False)]
        details = {k: v.to_dict(orient='records') for k, v in df_sub.groupby(mainBill)}
        for row in grouped_sum.itertuples():
            result_dict = dict()
            # print("row:", row)
            result_dict["billNo"] = getattr(row, mainBill).replace("\n", "").replace("-", "").strip()
            # print('result_dict["billNo"]:', result_dict["billNo"])
            ## 打开新增页面
            result_dict["billNo_prefix"], result_dict["billNo_suffix"] = result_dict["billNo"][:3], result_dict["billNo"][-8:]
            result_dict["flightCode"]  = getattr(row, self.__column_mapping.get("航班号航班日期", "航班号航班日期"))
            flightNo, flightDate = result_dict["flightCode"].split("/")
            flightNo = flightNo.replace("-", "").strip()
            result_dict["flightDate"] = datestrFormat(flightDate)
            result_dict["destPort"] = getattr(row, self.__column_mapping.get("货物目的地三字代码", "货物目的地三字代码"))
            result_dict["articles"] = sum(getattr(row, articles))
            result_dict["articles_arr"] = getattr(row, articles)
            result_dict["transferPort"] = getattr(row, transPort)
            result_dict["shipper2"] = getattr(row, shipper2)
            if pd.isna(getattr(row, weight)) or not getattr(row, weight):
                result_dict["weight"] = 100
            else:
                result_dict["weight"] = getattr(row, weight)
            result_dict["commodity"] = re.sub(r'[^a-zA-Z0-9]', ' ', getattr(row, PM))
            result_dict["cargoType"] = getattr(row, cargoType, "跨境电商").replace("快件","跨境电商")
            if re.match(r'^-?\d+(\.\d+)?$', str(getattr(row, volume, ''))):
                result_dict["volume"] = getattr(row, volume, '')
            # result_dict["file_measure_weight"] = 100
            if "普货" in result_dict["cargoType"]:
                result_dict["commodity_code"] = "9999"
                if re.match(r'^\d+\*\d+(?:\*\d+)*(?:[\s\t\r\n]+\d+\*\d+(?:\*\d+)*)*$', getattr(row, dimension, '')):
                    result_dict["dimension"] = getattr(row, dimension)
                else:
                    result_dict["dimension"] = "10*10*10*{}".format(result_dict["articles"])    
            else:
                result_dict["commodity_code"] = "1200"       
            if result_dict.get("volume"): #文件重量处理
                file_measure_weight = round(round(result_dict.get("volume"), 2) / 0.006)
                if file_measure_weight < 100 and result_dict.get("billNo_prefix") == "860": file_measure_weight = 100
                if file_measure_weight < 45 and result_dict.get("billNo_prefix") == "160": file_measure_weight = 45
                if file_measure_weight < result_dict.get("weight"): file_measure_weight = result_dict.get("weight")
                result_dict["file_measure_weight"] = file_measure_weight
            else:
                result_dict["file_measure_weight"] = result_dict.get("weight")
            
            result_dict["shipper"] = flightNo[:2].upper()
            result_dict["flight_number"] = flightNo[2:]
            result_dict["details"] = {k:v  for k,v in details.items() if k==result_dict["billNo"]}
            res.append(result_dict)
        print(res)
        return res
    
    def GEN(self, df):
        res = list()
        pingMing_head = self.__column_mapping.get("品名", "品名")
        flightNo_head = self.__column_mapping.get('航班号航班日期','航班号航班日期')
        billNo_head = self.__column_mapping.get("主单号", "主单号")
        articles_head = self.__column_mapping.get("件数", "件数")
        weight_head = self.__column_mapping.get("重量", "重量")
        print("head:",[billNo_head,  "件数", pingMing_head])
        print("df:", df[[billNo_head, "件数", pingMing_head]])
        grouped_sum = df.groupby([billNo_head, flightNo_head,
                  self.__column_mapping.get("货物目的地三字代码", "货物目的地三字代码"), pingMing_head,
                ], dropna=False).agg({articles_head: lambda x: list(x),weight_head: 'sum'}).reset_index()
        grouped_sum.columns = [billNo_head, flightNo_head, "货物目的地三字代码", pingMing_head, articles_head, weight_head]
        # df_sub = df.dropna(subset=['分单号'])
        # df_sub = df_sub[df_sub['分单号'].str.strip() != '']  # 删除空字符串或纯空格
        # 使用 str.contains + 正则过滤
        pattern = r'^[A-Za-z0-9]{2,20}$'
        df_sub = df[df["分单号"].str.contains(pattern, regex=True, na=False)]
        details = {k: v.to_dict(orient='records') for k, v in df_sub.groupby('主单号')}
        # self.quit()
        print("details:", details)
        for row in grouped_sum.itertuples():
            # print("row:", row)
            result_dict = dict()
            billNo = getattr(row, self.__column_mapping.get("主单号", "主单号")).replace("\n", "").strip()
            billNo = billNo.replace("-", "")
            result_dict["billNo"] = billNo
            result_dict["billNo_prefix"], result_dict["billNo_suffix"] = billNo[:3], billNo[3:]
            flightNo, flightDate = getattr(row, flightNo_head).split("/")
            flightNo = flightNo.replace("-", "").strip()
            result_dict["flightDate"] = datestrFormat(flightDate)
            result_dict["destPort"] = getattr(row, self.__column_mapping.get("货物目的地三字代码", "货物目的地三字代码"))
            result_dict["articles"] = sum(getattr(row, articles_head))
            result_dict["articles_arr"] = getattr(row, articles_head)
            result_dict["weight"] = getattr(row, weight_head) if getattr(row, weight_head) > 0 else 100
            result_dict["commodity"] = getattr(row, pingMing_head)
            result_dict["commodity_code"] = "9999"
            if re.match(r'^\d+$', getattr(row, self.__column_mapping.get("体积", "体积"), '')):
                result_dict["volume"] = getattr(row, self.__column_mapping.get("体积", "体积"), '')
            # result_dict["file_measure_weight"] = 100
            if re.match(r'^(([0-9]+\.?[0-9]*)\*){3}[0-9]+\.?[0-9]*$', getattr(row, self.__column_mapping.get("尺寸数量", "尺寸数量"), '')):
                result_dict["dimension"] = getattr(row, self.__column_mapping.get("尺寸数量", "尺寸数量"), '')
            else:
                result_dict["dimension"] = "10*10*10*{}".format(result_dict["articles"])
            result_dict["cargoType"] = "普货"
            result_dict["shipper"] = flightNo[:2].upper()
            result_dict["flight_number"] = flightNo[2:]
            result_dict["details"] = details
            res.append(result_dict)
        # print(result_dict)
        return res

    def PDD(self, row):
        result_dict = dict()
        billNo = getattr(row, self.__column_mapping.get("主单号", "主单号")).replace("\n", "").strip()
        billNo = billNo.replace("-", "")
        result_dict["billNo"] = billNo
        result_dict["billNo_prefix"], result_dict["billNo_suffix"] = billNo[:3], billNo[3:]
        flightNo, flightDate = getattr(row, self.__column_mapping.get("航班号航班日期", "航班号航班日期")).split("/")
        flightNo = flightNo.replace("-", "").strip()
        result_dict["flightDate"] = datestrFormat(flightDate)
        result_dict["destPort"] = getattr(row, self.__column_mapping.get("货物目的地三字代码", "货物目的地三字代码"))
        result_dict["transferPort"] = getattr(row, self.__column_mapping.get("中转港", "中转港"),"")
        result_dict["articles"] = getattr(row, self.__column_mapping.get("件数", "件数"))
        result_dict["weight"] = int(getattr(row, self.__column_mapping.get("重量", "重量")))
        result_dict["commodity"] = re.sub(r'[^a-zA-Z0-9]', ' ', getattr(row, self.__column_mapping.get("品名", "品名")))
        result_dict["commodity_code"] = "1200"
        result_dict["volume"] = getattr(row, self.__column_mapping.get("体积", "体积"), None)
        if result_dict.get("volume"):
            file_measure_weight = round(round(result_dict.get("volume"), 2) / 0.006)
            if file_measure_weight < 100 and (result_dict.get("shipper") == "W8" or result_dict.get("billNo_prefix") in ["489"]): file_measure_weight = 100
            if file_measure_weight < 45: file_measure_weight = 45
            if file_measure_weight < result_dict.get("weight"): file_measure_weight = result_dict.get("weight")
            result_dict["file_measure_weight"] = file_measure_weight
        else:
            result_dict["file_measure_weight"] = result_dict.get("weight")
        cargoType = getattr(row, self.__column_mapping.get("货物类型", "货物类型"))
        if "电商" in cargoType:
            result_dict["cargoType"] = "跨境电商"
        elif "普货" in cargoType:
            result_dict["cargoType"] = "普货"
        else:
            result_dict["cargoType"] = "跨境电商"
        result_dict["shipper"] = flightNo[:2].upper()
        result_dict["flight_number"] = flightNo[2:].replace("-", "")
        return result_dict

    def SHEIN(self, row):
        result_dict = dict()
        billNo= getattr(row, self.__column_mapping.get("主单号", "主单号")).replace("\n","").strip()
        billNo = billNo.replace("-","")
        result_dict["billNo"] = billNo
        result_dict["billNo_prefix"], result_dict["billNo_suffix"] = billNo[:3],billNo[3:]
        flightNo, flightDate = getattr(row, self.__column_mapping.get("航班号航班日期", "航班号航班日期")).split("/")
        flightNo = flightNo.replace("-", "").strip()
        result_dict["flightDate"] = datestrFormat(flightDate)
        result_dict["destPort"] = getattr(row, self.__column_mapping.get("货物目的地三字代码", "货物目的地三字代码"))
        result_dict["transferPort"] = getattr(row, self.__column_mapping.get("中转港", "中转港"),"")
        result_dict["articles"] = getattr(row, self.__column_mapping.get("件数", "件数"))
        result_dict["weight"]  = int(getattr(row, self.__column_mapping.get("重量", "重量")))
        result_dict["commodity"] = re.sub(r'[^a-zA-Z0-9]', ' ', getattr(row, self.__column_mapping.get("品名", "品名")))
        result_dict["commodity_code"] = "1200"
        result_dict["volume"] = getattr(row, self.__column_mapping.get("体积", "体积"), None)
        if result_dict.get("volume"):
            file_measure_weight = round(round(result_dict.get("volume"), 2) / 0.006)
            if file_measure_weight < 100 and (result_dict.get("shipper") == "W8" or result_dict.get("billNo_prefix") in ["489"]): file_measure_weight = 100
            if file_measure_weight < 45: file_measure_weight = 45
            if file_measure_weight < result_dict.get("weight"): file_measure_weight = result_dict.get("weight")
            result_dict["file_measure_weight"] = file_measure_weight
        else:
            result_dict["file_measure_weight"] = result_dict.get("weight")
        cargoType = getattr(row, self.__column_mapping.get("货物类型", "货物类型"), "跨境电商")
        if "电商" in cargoType:
            result_dict["cargoType"] = "跨境电商"
        elif "普货" in cargoType:
            result_dict["cargoType"] = "普货"
        else:
            result_dict["cargoType"] = "跨境电商"
        result_dict["shipper"] = flightNo[:2].upper()
        result_dict["flight_number"] = flightNo[2:]
        return result_dict

    def __validate(self, res):
        if res.get("destPort")=="":
            print("{},目的港信息不能为空！".format(res.get("billNo")))
            return False
        if res.get("billNo")=="":
            print("主单号不能为空！")
            return False
        return True

    def is_driver_quit(self):
        try:
            # 尝试获取当前URL（如果已退出会抛出异常）
            _ = self.__driver.current_url
            return False
        except:
            return True

    def get_failed(self):
        return self.__failed
    # 提取公共部分
    def fill_bill_content(self, sourceDataPath, res, tp: str="EXP"):
        ## 录单
        print("res:", res)
        if not self.__validate(res):
            self.quit()
            sys.exit(1)
        canSave = True
        canSend = False
        flag_go_on = True
        billNo = res.get("billNo")
        print("单号：", res.get("billNo_prefix"), res.get("billNo_suffix"), "开始填单")
        self.inputSelect(By.CSS_SELECTOR, 'input[placeholder="运单前缀"]', res.get("billNo_prefix"))
        el = self.inputSelect(By.CSS_SELECTOR, 'input[placeholder="运单号"]', res.get("billNo_suffix"))
        print(res.get("billNo_prefix"), res.get("billNo_suffix"))
        time.sleep(1)
        print(el.get_attribute("disabled"), el.is_enabled())
        if el.get_attribute("disabled"):
            self.giveUp(res.get("billNo"), "该单记录系统已经存在！")
            flag_go_on = False
            canSave = False
        elif not el.is_enabled():
            self.giveUp(res.get("billNo"), "验证不验证过！")
            flag_go_on = False
            canSave = False
        if flag_go_on and billNo != "":
            print("continue....")
            self.inputTxt(By.XPATH, '(//div[text()="航班号"])[2]/..//input[@placeholder="承运人"]', res.get("shipper"))
            self.inputTxt(By.XPATH, '(//div[text()="航班号"])[2]/..//input[@placeholder="航班号"]', res.get("flight_number"))
            # self.customSelect('(//label[text()="特货代码"])[1]/..//input', '//div/span[text()="EXP"]')
            # self.inputSelect(By.XPATH, '(//label[text()="特货代码"])[1]/..//input', "EXP")
            # elf.inputSelect(By.CSS_SELECTOR, 'input[placeholder="输入特货代码(多个用/分割)"]', tp)
            funcs = [
                partial(self.inputSelect, By.CSS_SELECTOR, 'input[placeholder="输入特货代码(多个用/分割)"]', tp),
            ]
            if not self.tryUntilCondition2(funcs, tp, "特货代码"):
                self.giveUp(res.get("billNo"), "输入特货代码失败！")
                return False
            funcs = [
                partial(self.customSelect, '(//label[text()="货物性质"])[1]/..//input', '(//li/span[text()="{}"])[1]'.format(res.get("cargoType"))),
            ]
            if not self.tryUntilCondition2(funcs, res.get("cargoType"),"货物性质"):
                self.giveUp(res.get("billNo"), "货物性质填写失败！")
                return False
            label_selection= ("交运营业点","国际出港")
            funcs = [
                partial(self.customSelect, '(//label[text()="{}"])[1]/..//input'.format(label_selection[0]), '(//li/span[text()="{}"])[1]'.format(label_selection[1])),
            ]
            if not self.tryUntilCondition2(funcs, label_selection[1], label_selection[0]):
                self.giveUp(res.get("billNo"), "{}填写失败！".format(label_selection[0]))
                return False

            # self.customSelect('(//label[text()="交运货站"])[1]/..//input', '(//li/span[text()="国际出港"])[1]')
            funcs = [
                partial(self.inputSelect, By.XPATH, '(//label[text()="品名代码"])[1]/..//input', res.get("commodity_code")),
                partial(self.customSelect, '(//label[text()="品名代码"])[1]/..//input', '//div/span[text()="{}"]'.format(res.get("commodity_code")))
            ]
            if not self.tryUntilCondition2(funcs, res.get("commodity_code"), "品名代码"):
                self.giveUp(res.get("billNo"), "品名代码填写失败！")
                return False
            # try:
            #     self.inputSelect(By.XPATH, '(//label[text()="品名代码"])[1]/..//input', res.get("commodity_code"))
            # except:
            #     self.customSelect('(//label[text()="品名代码"])[1]/..//input', '//div/span[text()="{}"]'.format(res.get("commodity_code")))
            funcs = [
                partial(self.inputTxt, By.CSS_SELECTOR,'input[placeholder="输入目的站"]', res.get("destPort"))
            ]
            if not self.tryUntilCondition2(funcs, res.get("destPort"), "目的站"):
                self.giveUp(res.get("billNo"), "目的站填写失败！")
                return False
            # self.inputTxt(By.CSS_SELECTOR, 'input[placeholder="输入目的站"]', res.get("destPort"))
            self.inputTxt(By.CSS_SELECTOR, 'input[placeholder="请输入预录入件数"]', res.get("articles"))
            if res.get("file_measure_weight"):
                self.inputTxt(By.CSS_SELECTOR, 'input[placeholder="请输入文件计重"]', res.get("file_measure_weight"))
            self.inputTxt(By.CSS_SELECTOR, 'input[placeholder="请输入预录入重量"]', res.get("weight"))
            # 中转逻辑
            if self.__platform != "Excel接口":
                transPort = ""
                if res.get("transferPort"):
                    transPort = res.get("transferPort")
                elif res.get("destPort").strip() not in ["YVR", "MIA"]:
                    transPort = "YVR"
                # elif res.get("billNo")[:3] == "369":
                #     transPort = "SCL"
                if transPort:
                    self.inputTxt(By.CSS_SELECTOR, 'input[placeholder = "承运人1"]', res.get("shipper"))
                    self.inputTxt(By.CSS_SELECTOR, 'input[placeholder = "航线到达站1"]', transPort)
                    self.inputTxt(By.CSS_SELECTOR, 'input[placeholder = "承运人2"]', res.get("shipper"))
                    self.inputTxt(By.CSS_SELECTOR, 'input[placeholder = "航线到达站2"]', res.get("destPort"))
            else:
                if res.get("transferPort"):
                    transPorts = res.get("transferPort").split(",") if res.get("transferPort") else []
                    extra_shippers = res.get("shipper2").split(",") if res.get("shipper2") else []
                    print("transPorts:{}, extra_shippers:{}".format(transPorts, extra_shippers))
                    # 非最后一个程
                    print("non-last leg....")
                    for i, transPort in enumerate(transPorts,1):
                        if i > 2: break
                        # default
                        shipper = res.get("shipper")
                        if i == 2:
                            if len(extra_shippers) > 0:
                                shipper = extra_shippers[0]
                        print("--shipper:{},transPort:{}".format(shipper, transPort))
                        self.inputTxt(By.CSS_SELECTOR, 'input[placeholder = "承运人{}"]'.format(i), shipper)
                        self.inputTxt(By.CSS_SELECTOR, 'input[placeholder = "航线到达站{}'.format(i), transPort)
                    # 最后一程
                    print("last leg....")
                    if len(transPorts) > 0:
                        transPort = res.get("destPort")
                        i = 3 if len(transPorts) > 2 else len(transPorts) + 1
                        shipper = res.get("shipper")
                        if len(extra_shippers) > 0:
                            shipper = extra_shippers[1] if len(extra_shippers) > 1 else extra_shippers[0]
                        print("--shipper:{},transPort:{}".format(shipper, transPort))
                        self.inputTxt(By.CSS_SELECTOR, 'input[placeholder = "承运人{}"]'.format(i), shipper)
                        self.inputTxt(By.CSS_SELECTOR, 'input[placeholder = "航线到达站{}'.format(i), transPort)

            if res.get("volume"): # 规则变为所有都要填体积
                self.inputTxt(By.CSS_SELECTOR, 'input[placeholder="请输入预录入体积"]', res.get("volume"))
            self.inputSelect(By.XPATH, '(//label[text()="航班日期"])[1]/..//input', res.get("flightDate"))
            self.inputTxt(By.CSS_SELECTOR, 'input[placeholder="请输入货物品名(英文)"]', keep_english_digits_space(res.get("commodity")))
            # print('(//div/span[text()="{}"])'.format(self.__receiver))
            funcs = [
                partial(self.inputSelect, By.XPATH, '(//label[text()="交货人"])[1]/..//input',self.__receiver),
                partial(self.customSelect, '(//label[text()="交货人"])[1]/..//input', '(//div/span[text()="{}"])'.format(self.__receiver))
            ]

            if not self.tryUntilCondition2(funcs, self.__receiver, "交货人"):
                self.giveUp(res.get("billNo"), "交货人填写失败！")
                return False
            # 新增固定字段
           # self.inputSelect(By.XPATH, '(//label[text()="交运货站"])[1]/..//input', self.__receiving_station)
            # 规格录入，目前仅普货有
            if res.get("dimension") and tp == "GEN":
                d_arr = res.get("dimension").split()
                for idx,dm in enumerate(d_arr):
                    WebDriverWait(self.__driver, 10).until(EC.element_to_be_clickable((By.XPATH, "//button/span[text()=' 新增尺寸 ']"))).click()
                    tr_element=WebDriverWait(self.__driver, 10).until(EC.visibility_of_element_located((By.XPATH, "(//div[text()='预录尺寸信息']/../..//tr)[{}]".format(2+idx))))
                    input_elements = tr_element.find_elements(By.TAG_NAME, 'input')
                    s_arr = dm.split("*")
                    for i in range(4): # range包括0，但不包括最后一个
                        input_elements[i].send_keys(lget(s_arr,i,1 if i<3 else res["articles_arr"][idx]))
        # 附件
        if not self.__headless == "Y" and self.__login_info.get('upload_attachment') == "Y" and tp != "GEN":
            # 附件，代码目录或数据源目录查找
            current_path = os.getcwd()
            if self.__login_info.get("attachment_name_auto","N") == "Y":
                # script_dir = Path(__file__).parent.resolve()
                filePath = rename_with_pathlib(target_dir = Path(current_path), filename=f"{billNo}.xlsx")
            else:
                fileName = self.__login_info.get("attachment_name") or "0801.xlsx"
                print("filename：", fileName)
                if os.path.exists(current_path):
                    filePath = os.path.join(current_path, fileName)
                else:
                    filePath = os.path.join(os.path.dirname(sourceDataPath), fileName)
                # current_path = os.path.dirname(os.path.abspath(__file__))

            filePath = str(filePath).replace('/', "\\")
            print("附件目录filePath：", filePath)

            updated_status = self.customUpload((By.XPATH, '//li/span[text()="快跨货物清单"]'),
                                        win_upload=self.pyautogui_upload, filePath=filePath,
                                        check_if_exists=(not flag_go_on))
            if updated_status == 2:
                print(f"{billNo}, 附件已存在...")
                canSend = True
            elif updated_status == -1:
                print(f"{billNo}, 附件上传报错...")
                self.giveUp(res.get("billNo"), "附件上传失败！！")
                canSend = False
            else:
                print(f"{billNo}, 附件上传正常...")
                canSend = True
                canSave = True
        # 保存与发送
        if canSave:
            if not self.save():  canSend = False  # 保存失败，则不发送
            print(f"{billNo}, 主单保存完成...")
            # 分单录入
            # print("details:", res.get("details"))
            # 发送成功确认
            if not self.check_element_if_exist('//div[@class="el-notification__content"]/p[contains(text(), "保存成功")]',3):
                self.giveUp(res.get("billNo"), "保存失败！")
                return False

        if res.get("details"):    
            self.fill_bill_details(res)
        else:
            print("没有分单....")
            if billNo in self.__failed: del self.__failed[billNo]
        # input()
        if canSend:
            print("self.__headless:{},upload_attachment:{}，direct_submit：{}".format(self.__headless, self.__login_info.get('upload_attachment'), self.__login_info.get('direct_submit')))
            if not self.__headless == "Y" and self.__login_info.get('upload_attachment') == "Y" and self.__login_info.get('direct_submit') == "Y":
                print("sending {}....".format(billNo))
                self.send(billNo)
        return True

    def fill_bill_details(self, res:dict):
        print("分单录入开始.....")
        max_try_times = 6
        self.__driver.find_element(By.XPATH, "//div[text()='分单交运预录入']").click()
        subBill_name = self.__column_mapping.get("分单号", "分单号")
        typeCode_selector = (By.XPATH, '(//label[text()="特货代码"])[2]/..//input')
        weight_selector = (By.XPATH, '(//label[text()="重量"])[1]/..//input')
        weight_selector2 = (By.XPATH, '(//label[text()="重量"])[2]/..//input')
        for k,v in res.get("details").items():
            try:
                j = 0
                while True:
                    print("....新的loop开始....")
                    #input()
                    # 主单,多单录入时双保险，防止相互影响
                    WebDriverWait(self.__driver, 10).until(EC.visibility_of_element_located(typeCode_selector)).clear()
                    self.inputSelect(By.XPATH, '//*[@id="pane-third"]/div/form[1]/div/div/div//input',k.replace("-",""))
                    wgt_el = self.__driver.find_element(weight_selector[0], weight_selector[1])
                    typeCod_el = self.__driver.find_element(typeCode_selector[0], typeCode_selector[1])
                    WebDriverWait(self.__driver, 10).until(lambda driver: wgt_el.get_attribute("value") != "")
                    WebDriverWait(self.__driver, 10).until(lambda driver: typeCod_el.get_attribute("value") != "")
                    print('wgt_el.get_attribute("value"):', wgt_el.get_attribute("value"))
                    total_weight = int(wgt_el.get_attribute('value'))
                    print("total_weight:", total_weight)
                    ## 已录入记录检查 --> 直到没有剩余
                    try:    
                        if j > max_try_times:
                            print(f".....尝试已超过{max_try_times}次，放弃尝试！.....")
                            self.giveUp(k, "其它原因：无法完成分单录入且超过最大尝试限制！")
                            break
                        else:
                            j += 1
                            print(".....分单记录录入无限循环，第{}次尝试开始.....".format(j))
                        sub_bills_done = set()
                        weight_filled = 0
                        if self.check_element_if_exist('(//table[@class="el-table__body"])[5]/tbody/tr',3):
                            result_els = self.__driver.find_elements(By.XPATH, '(//table[@class="el-table__body"])[5]/tbody/tr')
                            # print("result_els:", result_els[0].get_attribute("innerHTML"))
                            #input()
                            for e in result_els:
                                #print("e:",e.get_attribute("innerHTML"))
                                e1 = e.find_element(By.CSS_SELECTOR, "td.el-table_5_column_45>div")
                                e2 = e.find_element(By.CSS_SELECTOR, "td.el-table_5_column_53>div")
                                print("e1.innerHTML:{},e2.inneinnerHTMLrhtml:{}",e1.get_attribute("innerHTML"),e2.get_attribute("innerHTML"))
                                print("e1.text:{},e2.text:{}", e1.text, e2.text)
                                print("e2.pruned_innerHTML:",re.sub("[<\-!>]","",e2.get_attribute("innerHTML")))
                                if e1: sub_bills_done.add(e1.text if e1.text else re.sub("[<\-!>]","",e1.get_attribute("innerHTML")))
                                if e2:
                                    weight_filled += int(e2.text if e2.text else re.sub("[<\-!>]","",e2.get_attribute("innerHTML")))
                        print("sub_bills_done:", sub_bills_done)
                        print("weight_filled:", weight_filled)
                        #input
                        rest = len(v) - len(sub_bills_done)
                        if rest > 0:
                            rest_weight = total_weight-weight_filled
                            print("rest_weight:", rest_weight)
                            weight_parts = split_random_number(int(rest_weight), rest) if  rest > 1 else [rest_weight] #只有一份时，不需要分等份
                            print("weight_splited_parts:", weight_parts)
                            for i, row in enumerate([row for row in v if row.get(subBill_name).split("-")[0] not in sub_bills_done]):
                                # 填写主单，并等待依赖渲染    
                                #input
                                WebDriverWait(self.__driver, 10).until(EC.visibility_of_element_located(typeCode_selector)).clear()
                                #input
                                self.inputSelect(By.XPATH, '//*[@id="pane-third"]/div/form[1]/div/div/div//input',k.replace("-",""))
                                typeCod_el = self.__driver.find_element(typeCode_selector[0], typeCode_selector[1])
                                WebDriverWait(self.__driver, 10).until(lambda driver: typeCod_el.get_attribute("value") != "")
                                #input()
                                # 分单目的港 与 主单目的港 不一致，在分单中填写分单目的港信息
                                # subBill_arr = row.get(subBill_name).split("-")
                                subBill_arr = re.split(r'[- ]', row.get(subBill_name))
                                sub_bill_no = subBill_arr[0]
                                print("第{}次尝试之,{}-{}".format(j,i,sub_bill_no))
                                self.inputTxt(By.CSS_SELECTOR, 'input[placeholder="分单号"]', sub_bill_no)
                                #input()
                                #sleep(4)
                                if len(subBill_arr) > 1:
                                    print("subBill_arr[1]:", subBill_arr[1])
                                    destPort = subBill_arr[1]
                                else:
                                    destPort = row.get(self.__column_mapping.get("分单目的港", "分单目的港"))
                
                                destPort_selector = (By.XPATH, '//label[text()="航程目的站"]/..//input')
                                destPort_el = self.__driver.find_element(destPort_selector[0], destPort_selector[1])
                                # 使用 JavaScript 直接修改元素的 value 属性; 有隐藏操作，要界面操作才行
                                ##  self.__driver.execute_script("arguments[0].value = '{}';".format(row.get(self.__column_mapping.get("分单目的港", "分单目的港")).strip()), el)
                                if not destPort:
                                    destPort = res.get("destPort")
                                    print("分单目的港为空, 尝试使用主单目的港:{}！".format(destPort))
                                # func = partial(self.customSelect2, destPort_selector[1], '//label[text()="机场三字码"]/..//input', value=destPort)
                                func_dpt = partial(self.inputSelect, destPort_selector[0], destPort_selector[1], destPort)
                                self.tryUntilCondition(func_dpt, destPort_el ,destPort, 13, "分单目的港")
                                #input
                                print("件数:", row.get(self.__column_mapping.get("件数", "件数")))
                                self.inputTxt2(By.CSS_SELECTOR, 'input[placeholder="请输入件数"]',row.get(self.__column_mapping.get("件数", "件数")))
                                #input
                                print("[重量]str(weight_parts[i]:{}，i:{}".format(weight_parts,i))
                                wgt = row.get(self.__column_mapping.get("重量", "重量")) or str(weight_parts[i])
                                el_wgt = self.__driver.find_element(*weight_selector2)
                                func_wgt = partial(self.inputTxt2,weight_selector2[0],weight_selector2[1],str(wgt))
                                self.tryUntilCondition(func_wgt, el_wgt ,str(wgt), 13, "重量")
                                self.tryUntilCondition(func_dpt, destPort_el ,destPort, 13, "分单目的港")
                                self.tryUntilCondition(func_wgt, el_wgt ,str(wgt), 13, "重量")
                                # sleep(1)
                                # input()
                                print("序号{}分单：{}，保存！".format(i+1,sub_bill_no))
                                self.__driver.find_element(By.XPATH, '//button/span[text()="保存分单信息 "]').click()
                                # 等待保存后，重置字段为空白
                                WebDriverWait(self.__driver, 10).until(lambda driver: typeCod_el.get_attribute("value") == "")
                                #input()
                        else:
                            print("第{}次尝试，因已经没有需要录入的分单，直接退出...".format(j))
                            print("self.__failed:",self.__failed)
                            print("k:", k)
                            if k in self.__failed: del self.__failed[k]
                            break
                        sleep(1)
                        print("....一个loop结束....")
                        # input
                    except:
                        traceback.print_exc()
                        print("程序异常，忽略重试！")
                        body = self.__driver.find_element(By.TAG_NAME, "body")
                        body.send_keys(Keys.ESCAPE)
                        body.send_keys(Keys.ESCAPE)
                        continue
            except Exception as e:
                error_message = traceback.format_exc()
                self.giveUp(k,"其它原因：{}".format(error_message))
            finally:
                # 切回主单界面
                self.tag_return()
                #self.__driver.find_element(By.XPATH, "//div[text()='主单预录入']").click()
            #input

    def bill_fill(self, df, taskList: set, sourceDataPath: str, test=False):
        self.open_menu()
        print("excel data transfer...")
        self.__failed = dict()
        res = dict()
        if self.__platform == "GEN":
            res_arr = self.GEN(df); tp = "GEN"                
        elif self.__platform == "Excel接口":
            res_arr = self.Excel(df)
        else:
            res_arr = df.itertuples(); tp = "EXP"
        for row in res_arr:
            try:
                if self.__platform == "SHEIN":
                    res = self.SHEIN(row)
                elif self.__platform == "GEN" or self.__platform == "Excel接口":
                    res = row
                else:
                    res = self.PDD(row)
                if "普货" in res["cargoType"]:
                    tp = "GEN"
                else:
                    tp = "EXP"
                if not res.get("billNo") or res.get("billNo") not in taskList:
                    print("{}，不在任务中，安全退出！".format(res.get("billNo")))
                    continue
                # 新增
                self.page_add()
                # 运单，是否可以继续的标志，提前获取
                WebDriverWait(self.__driver, 10).until(
                        EC.visibility_of_element_located((By.CSS_SELECTOR, 'input[placeholder="运单号"]')))
                print("{}, 开始...".format(res.get("billNo")))
            except Exception as e:
                print("Exception0....")
                traceback.print_exc()
                self.page_turned()
                continue
            # 填单的主休代码
            try:
                self.fill_bill_content(sourceDataPath, res, tp)
            except ElementClickInterceptedException or TimeoutException or Exception:
                print("exception1...")
                error_message = traceback.format_exc()
                self.giveUp(res.get("billNo"), "超时或拦截：{}".format(error_message))
            except:
                print("exception2...")
                error_message = traceback.format_exc()
                self.giveUp(res.get("billNo"), "其它原因：{}".format(error_message))
            finally:
                print("finally.....")
                if self.is_driver_quit():
                    return False
                ## close old page
                self.page_turned()
        if test:
            time.sleep(10000) or input()

    def yupei_fill(self, df, taskList: set, sourceDataPath: str, test=False):
        self.openWebsite("https://zj.singlewindow.cn/air/houston_front/prepackagedManifest/PMmainDataPage")
        print("excel data transfer...")
        df = df.sort_values(by="航班编号")
        self.__failed = dict()
        res = dict()
        if self.__platform == "GEN":
            res_arr = self.GEN(df)              
        else:
            # 分单号汇总
            filtered_df = df[df["分单号"].notna() & (df["分单号"] != "")]
            if not filtered_df.empty:
                print("存在分单号，进行汇总...")
                sum_df = filtered_df.groupby("主单号").agg({
                    "件数": "sum", "重量": "sum", "体积": "sum",
                    "航班编号": "max", "航班日期": "max", "目的港": "max", "发货人地址": "max", "发货人": "max", "品名": "max",
                    "收货人": "max", "收货人地址": "max", "发货人国家": "max", "收货人国家": "max", "类型": "max", "中转港": "max",
                }).reset_index()  # 重置索引，使主单号变成普通列
                sum_df['分单号'] = ""
                df = pd.concat([df, sum_df], ignore_index=True)
                print(df)
            res_arr = df.itertuples()
        flightCode = ""
        for row in res_arr:
            billNo = getattr(row, self.__column_mapping.get("主单号", "主单号"))
            if not billNo or billNo not in taskList:
                print("{}，不在重试任务中，跳过！".format(billNo))
                continue
            try:
                if self.__platform == "SHEIN":
                    res = self.SHEIN(row)
                elif self.__platform == "GEN":
                    res = row
                elif self.__platform == "Excel接口":
                    res = self.Excel_yupei(row)
                else:
                    res = self.PDD(row)
                if "普货" in res["cargoType"]:
                    tp = "GEN"
                else:
                    tp = "EXP"
            except Exception as e:
                print("Exception0....")
                traceback.print_exc()
                self.page_turned('//div/span[text()="预配舱单主要数据"]')
                continue
             # 填单的主体代码
            try:
                # 一次多单时，判断当前与上一单是否同主单号
                print("上个编号：{}，当前编号：{}".format(flightCode, res.get("flightCode")))
                if flightCode and flightCode == res.get("flightCode"):
                    print("沿用头信息...")
                    openNewFlightCode = False
                else:
                    print("创建头信息...")
                    openNewFlightCode = True

                if openNewFlightCode == True or self.__login_info["preCheck"] == "Y":
                    if flightCode == "":
                        self.page_turned('//div/span[text()="预配舱单主要数据"]')
                    else:
                        self.__driver.find_element(By.XPATH,
                                                   '(//span[text()="预配舱单主要数据" and not (contains(@class, "active")) and contains(@class, "tags-view-item")])[1]').click()

                flightCode = self.fill_yupei_content(res, tp, openNewFlightCode)
                # input()
                # self.quit()
            except ElementNotInteractableException or JavascriptException:
                print("exception1...")
                traceback.print_exc()
            except ElementClickInterceptedException or TimeoutException or Exception:
                print("exception2...")
                error_message = traceback.format_exc()
                traceback.print_exc()
        if test:
            time.sleep(10000) or input()


    def fill_yupei_submit(self, billNo):
        print('submit.....')
        try:
            result_detail = self.fast_find_elements(self.__driver, "div.el-table__body-wrapper>table.el-table__body>tbody>tr")
            print("len(result_detail):", len(result_detail))
            if len(result_detail) > 0:
                # 分页展开，按单号查找记录（控制选100条/页一次）
                if not self.check_element_if_exist('//li[contains(@class,"el-select-dropdown__item selected")]//span[text()="100条/页"]'):
                    self.__driver.find_element(By.XPATH, '//div[@class="pagination"]//i').click()
                    self.check_element_if_exist('//li[contains(@class,"el-select-dropdown__item")]//span[text()="100条/页"]').click()
                    time.sleep(3)
                rows = result_detail[0].find_elements(By.XPATH, '//button/span[text()="{}"]/../../../..'.format(billNo))
                print("len(rows):", len(rows))
                # fprint(rows[0].get_attribute("innerHTML"), 'rows_0.txt')
                if rows:
                    if "已保存" in rows[0].get_attribute("innerHTML"):
                        # 申报
                        print('选择并申报....')
                        el_ckb = result_detail[0].find_elements(By.XPATH, '//button/span[text()="{}"]/../../../..//input[@type="checkbox"]/..'.format(billNo))
                        el_ckb[0].click()
                        self.__driver.find_element(By.XPATH, '//span[text()="申报"]').click()
                        if self.check_element_if_exist(
                                '//div[@class="el-message-box__message"]/p[contains(text(), "是否确认申报")]'):
                            self.__driver.find_element(By.XPATH, '//span[contains(text(),"确定")]').click()
                        if self.check_element_if_exist(
                                '//div[@class="el-message-box__message"]/p[contains(text(), "重复") or contains(text(), "有未保存")]'):
                            self.__driver.find_element(By.XPATH, '//span[contains(text(),"确定")]').click()
                    else:
                        if "审批" in rows[0].get_attribute("innerHTML"):
                            self.giveUp(billNo, "该单记录系统已经存在且已申报！")
                        else:
                            self.giveUp(billNo, "未保存成功！")
            # time.sleep(333333)
        except:
            traceback.print_exc()
            raise Exception("{}，申报失败failed!".format(billNo))


    def fill_yupei_content(self, res, tp, openNewFlightCode):
        try:
            # 按主单号查询是否已填过单检查
            print('login_info["preCheck"] :', self.__login_info.get("preCheck"))
            #input()
            if self.__login_info.get("preCheck") == "Y":
                self.inputTxt(By.XPATH,'//label[text()="提运单号"]/../div//input', res.get("billNo"))
                self.inputTxt(By.XPATH,'//label[text()="航次航班号"]/../div//input', "")
                self.__driver.find_element(By.XPATH, '//span[text()="查询"]').click()
                time.sleep(1)
                result_els = self.fast_find_elements(self.__driver, "div.el-table__body-wrapper>table.el-table__body>tbody>tr")
                # 能搜索到，基本上是已存在的
                if len(result_els) > 0:
                    # print("len(result_els):", len(result_els))
                    # print(result_els[0].get_attribute("innerHTML"))
                    # 等待跳转到详细页面
                    result_els[0].find_element(By.XPATH, '//span[text()=" 详情 "]').click()
                    WebDriverWait(self.__driver, 30).until(
                        EC.presence_of_all_elements_located((By.XPATH, '//label[text()="航次航班编号"]')))
                    # 申报
                    self.fill_yupei_submit(res.get("billNo"))
                    return False

            # 源数据处理
            customCode = "2910" if res.get("cargoType") == "普货" else "2916"
            # 填公共信息
            def search_if_exist(flightCode:str):
                self.inputTxt(By.XPATH, '//label[text()="提运单号"]/../div//input', "")
                self.inputTxt(By.XPATH,'//label[text()="航次航班号"]/../div//input', flightCode)
                self.__driver.find_element(By.XPATH, '//span[text()="查询"]').click()
                time.sleep(3)  ## 防止查询未同步导致的问题;
                result_els = self.fast_find_elements(self.__driver, "div.el-table__body-wrapper>table.el-table__body>tbody>tr")
                # result_els = WebDriverWait(self.__driver, 3).until(EC.presence_of_all_elements_located((By.CSS_SELECTOR, "//label[text()="航次航班编号"]")))
                # result_els = self.__driver.find_elements(By.CSS_SELECTOR, "div.el-table__body-wrapper>table.el-table__body>tbody>tr")
                print("len(result_els):", len(result_els))
                if len(result_els) == 0:
                    return False
                else:
                    el = result_els[0]
                    ## 防止查询未同步导致的问题; 再次新建公共信息处理
                    code = el.find_element(By.XPATH,"(//td)[2]").get_attribute("innerHTML")
                    print("code html:", code)
                    if flightCode in code:
                        ## 进去进一步判断
                        el.find_element(By.XPATH, '//span[text()=" 详情 "]').click()
                        custom_code = WebDriverWait(self.__driver, 10).until(
                            EC.visibility_of_element_located((By.XPATH, '//label[text()="离境地关区"]/../div//input')))
                        WebDriverWait(self.__driver, 10).until(lambda driver: custom_code.get_attribute("value") != "")
                        print('custom_code.get_attribute("value"):', custom_code.get_attribute("value"))
                        ## 已有头信息的，要判断customCode是否一致
                        if customCode in custom_code.get_attribute("value"):
                            print("实际存在，无需新增......")
                            return True
                        else:
                            # 重新返回，因为严格来说还是不存在
                            self.__driver.find_element(By.XPATH, '//a/li/span[text()="预配舱单主要数据"]').click()
                            return False
                    else:
                        return False

            ## 不存在则新增公共信息 或 已切换tab情况
            if openNewFlightCode == True or self.__login_info["preCheck"] == "Y":
                ### 可能新增会，再次search确认，存在则铺开；不存在新增
                if not search_if_exist(res.get("flightCode")):
                    self.page_add('(//button[@class="el-button el-button--primary el-button--mini"])[1]')
                    self.inputTxt(By.XPATH,'//label[text()="航次航班编号"]/../div//input', res.get("flightCode"))
                    tasks = [
                        partial(self.inputTxtEnter,By.XPATH,'//label[text()="离境地关区"]/../div//input', customCode,3),
                        partial(self.inputTxtEnter,By.XPATH,'//label[text()="离境地海关代码"]/../div//input', "HGH",3),
                     ]
                    self.batchTryWithCheck(tasks)
                    ## 保存
                    self.__driver.find_element(By.XPATH, '//button/span[text()="保存"]').click()
                    self.check_element_if_exist('//div[@role="alert"]/p[contains(text(), "已保存")]')
            # 仅输入表头的情况        
            if self.__login_info.get("onlyFlightCode")=="Y": return res.get("flightCode")
            
            print("主体信息....")    
            # input()
            # 详情基本信息
            def add_and_wait(add_xpath_selector:str, first_input_xpath_selector:str):
                # body = self.__driver.find_element(By.TAG_NAME, "body")
                # body.send_keys(Keys.ESCAPE)
                # body.send_keys(Keys.ESCAPE)
                self.__driver.find_element(By.XPATH, add_xpath_selector).click()
                WebDriverWait(self.__driver, 5).until(EC.presence_of_element_located((By.XPATH, first_input_xpath_selector))) 
            tryUntilSuccess([partial(add_and_wait, '//button/span[text()="新增"]', '//label[text()="总提运单号"]/../div//input')], "主体信息addnew_and_wait", 3)
            # input()
            print("【{}】，开始....".format(res.get("billNo")))
            self.inputTxt(By.XPATH,'//label[text()="总提运单号"]/../div//input', res.get("billNo"))
            if res.get("subBillNo"): self.inputTxt(By.XPATH, '//label[text()="分提运单号"]/../div//input', res.get("subBillNo"))
            self.inputTxt(By.XPATH,'//label[text()="货物装载时间"]/../div//input', "{} 23:00:00".format(res.get("flightDate"))) 
            tasks = [
                partial(self.inputTxtChoose,'//label[text()="卸货地代码"]/../div//input','//span[text()="{}"]'.format(res.get("destPort")), res.get("destPort")),
                partial(self.inputTxtEnter,By.XPATH,'//label[text()="装货地关区"]/../div//input', customCode, 2),
                partial(self.inputTxtEnter,By.XPATH,'//label[text()="装货地代码"]/../div//input', "HGH", 2),
                partial(self.inputTxtEnter,By.XPATH,'//label[text()="货物托运地点或国家"]/../div//input', "HGH", 2),
                partial(self.inputTxtEnter,By.XPATH,'//label[text()="卸货地代码"]/../div//input', res.get("destPort"), 2),
                partial(self.inputTxtChoose,'//label[text()="运费支付方法代码"]/../div//input','//span[text()="PP"]', "PP"), 
                partial(self.inputTxtChoose,'(//label[text()="国家(地区)代码"]/../div//input)[1]','(//li[@class="el-select-dropdown__item"]//span[text()="CN"])[5]', "CN"), 
                partial(self.inputTxtChoose,'(//label[text()="国家(地区)代码"]/../div//input)[2]','(//span[text()="{}"])[last()]'.format(res.get("recieverCountry")), res.get("recieverCountry")), 
            ]
            self.batchTryWithCheck(tasks,6,2)
            # input()
            self.inputTxt(By.XPATH,'//label[text()="托运货物件数"]/../div//input', res.get("articles")) 
            self.inputTxt(By.XPATH,'//label[text()="货物总毛重(kg)"]/../div//input', res.get("weight")) 
            if res.get("cargoType") == "普货":
                self.__driver.find_element(By.XPATH, '//span[text()="进出口货物"]').click()
            else:
                self.__driver.find_element(By.XPATH, '//span[text()="快件"]').click()
            self.inputTxt(By.XPATH,'//label[text()="发货人名称"]/../div//input', res.get("sender")) 
            #self.inputTxt(By.XPATH,'(//label[text()="国家(地区)代码"]/../div//input)[1]', res.get("senderCountry")) 
            self.inputTxt(By.XPATH,'(//label[text()="详细地址"]/../div//input)[1]', res.get("senderAddr")) 
            self.inputTxt(By.XPATH,'//label[text()="收货人名称"]/../div//input', res.get("reciever")) 
            #self.inputTxt(By.XPATH,'(//label[text()="国家(地区)代码"]/../div//input)[2]', res.get("recieverCountry")) 
            self.inputTxt(By.XPATH,'(//label[text()="详细地址"]/../div//input)[2]', res.get("recieverAddr")) 
            self.__driver.find_element(By.XPATH, '(//button/span[text()="保存"])[3]').click()
            is_repeat = False
            if not self.check_element_if_exist('//div[@role="alert"]/p[contains(text(), "已保存")]'):
                if self.check_element_if_exist('//div[@class="el-message-box__message"]/p[contains(text(), "重复")]'):
                    self.__driver.find_element(By.XPATH, '//div[@class="el-message-box__message"]/p[contains(text(), "重复")]/../../../../div/button').click()
                    is_repeat = True
            # 商品信息
            try:
                tryUntilSuccess([partial(add_and_wait, '//div[@class="el-tabs__item is-top" and text()="商品项信息"]', '//label[text()="商品项序号"]/../div//input')], "商品信息addnew_and_wait", 6)
                # print(1111)
            except:
                if self.check_element_if_exist('//div[@class="el-message-box__message"]/p[contains(text(), "重复")]'):
                    self.giveUp(res.get("billNo"),"该单记录系统已经存在！")
                else:
                    self.giveUp(res.get("billNo"),"商品信息tab切换timeout!")
                return res.get("flightCode")
                # traceback.print_exc()
            # 基本信息填写了但商品信息没填的情况    
            sure_data_not_exist = False
            if is_repeat:
                if self.fast_find_elements(self.__driver,'(//div[contains(@class,"el-table__body-wrapper")]//table)[2]//tr'): sure_data_not_exist=True
            if (not is_repeat) or sure_data_not_exist:
                self.inputTxt(By.XPATH,'//label[text()="商品项序号"]/../div//input', 1) 
                self.inputTxt(By.XPATH,'//label[text()="货物件数"]/../div//input', res.get("articles")) 
                self.inputTxt(By.XPATH,'//label[text()="货物毛重(kg)"]/../div//input', res.get("weight")) 
                self.inputTxt(By.XPATH,'//label[text()="货物简要描述"]/../div//input', res.get("pingMing")) 
                self.__driver.find_element(By.XPATH, '(//button/span[text()="保存"])[2]').click()
                if not self.check_element_if_exist('//div[@role="alert"]/p[contains(text(), "已保存")]'):
                    self.__driver.find_element(By.XPATH, '(//button/span[text()="保存"])[2]').click()
            # game over
            self.__driver.find_element(By.XPATH, '(//i[@class="el-dialog__close el-icon el-icon-close"])[1]').click()
            # 保存后继续申报
            if self.__login_info.get("preCheck") == "Y":
                print('申报....')
                self.fill_yupei_submit(res.get("billNo"))
        except:
            self.giveUp(res.get("billNo"), "其它原因")
            traceback.print_exc()
        # input("wait a minute!")
        finally:
            return res.get("flightCode")
        
    def bill_fill_simple(self, df, taskList: set, sourceDataPath: str, test=False):
        print("simple....")
        self.open_menu()
        self.page_add()
        print("excel data transfer...")
        self.__failed = dict()
        res = dict()
        for row in df.itertuples():
            try:
                if self.__platform == "SHEIN":
                    res = self.SHEIN(row)
                elif self.__platform == "Excel接口":
                    res = self.Excel(row)
                elif self.__platform == "GEN":
                    res = self.GEN(row)
                else:
                    res = self.PDD(row)
                if not res.get("billNo") or res.get("billNo") not in taskList:
                    print("{}，不在任务中，安全退出！".format(res.get("billNo")))
                    continue
                # 运单，是否可以继续的标志，提前获取
                mainBill_el = WebDriverWait(self.__driver, 10).until(
                        EC.visibility_of_element_located((By.CSS_SELECTOR, 'input[placeholder="运单号"]')))
                print("{},开始...".format(res.get("billNo")))
            except Exception as e:
                print("Exception0....")
                traceback.print_exc()
                self.page_next()
                continue
            try:
                # 填单的主休代码
                print("sourceDataPath:",sourceDataPath)
                self.fill_bill_content(sourceDataPath, res)
            except ElementNotInteractableException or JavascriptException:
                print("exception1...")
                error_message = traceback.format_exc()
                self.giveUp(res.get("billNo"), "拦截：{}".format(error_message))
                # self.data_check(billNo=res.get("billNo"), mainBill_el=mainBill_el)
            except ElementClickInterceptedException or TimeoutException or Exception:
                print("exception2...")
                error_message = traceback.format_exc()
                self.giveUp(res.get("billNo"), "超时：{}".format(error_message))
                # if self.data_check(billNo=res.get("billNo"), mainBill_el=mainBill_el):
                #     try:
                #         warning = WebDriverWait(self.__driver, 4, ignored_exceptions=(TimeoutException,NoSuchElementException,)).until(
                #             EC.visibility_of_element_located((By.XPATH, 'p[contains(text(), "运单已存在")]')))
                #         if warning:
                #             self.giveUp(res.get("billNo"), "该单记录系统已经存在！")
                #     except:
                #         self.giveUp(getattr(row, self.__column_mapping.get("主单号","主单号")), "其它原因：{}".format(error_message))
            except:
                error_message = traceback.format_exc()
                self.giveUp(res.get("billNo"), "其它原因：{}".format(error_message))
            finally:
                if self.is_driver_quit():
                    return False
                self.page_next()
        self.page_turned()
        if test:
            time.sleep(10000) or input()

    def bill_fill_entry(self,mode, **kwargs):
        if mode == "yupei":
            #self.bill_fill_simple(**kwargs)
            self.yupei_fill(**kwargs)
        else:
            self.bill_fill(**kwargs)

    def train(self, sample_num: int, output_dir: str):
        # 获取文件夹中的文件列表，并过滤特定类型的文件（例如 .txt 文件）
        file_list = [int(re.search(r'\d+', f).group()) for f in os.listdir(output_dir) if re.search(r'\d+', f)]
        file_list.sort(reverse=True)
        last_num = file_list and file_list[0]
        print(last_num)
        start = last_num if last_num else 0
        if start <= sample_num:
            self.openWebsite()
            self.inputUsername(By.NAME, "username")
            self.inputPassword(By.NAME, "password")
            for i in range(start, sample_num):
                # 保存图片到本地
                image, captcha_element = self.getVerifyImg(By.CSS_SELECTOR, "div.logo-img>img")
                image_path = os.path.join(output_dir, f'captcha_{i + 1}.png')
                image.save(image_path)
                print(f"保存验证码图片: {image_path}")
                # 刷新页面以获取新的验证码
                captcha_element.click()
                time.sleep(1)  # 等待页面加载
        print("{},采集完成！".format(sample_num))

    def quit(self):
        self.__driver.quit()
        self.__driver.service.process.terminate()  # 确保 chromedriver 进程终止
        #sys.exit(1)
